classdef ziModule < handle
    %ZIMODULE abstract class for different modules
    %   parent class of ziPlotter, ziScope, ziSpectrum and ziSweeper
    
    properties
        name   % module name
        device % device name
        hdl    % module handle
        
        setting_structure          % settings
        setting_name_readonly      % read-only settings
        
        clockbase                  % device clockbase
        available_demod_channel    % device demod channels
        
        subscribed_paths           % subscribed path list
        
        monitor_hdl   % monitor axis handle
        curves        % monitor curve object        
        curve_info   % information of monitor curves

        measured_data % measured data
        session       % session
        zi            % zi device handle
    end
    properties (Access = private)
        
    end
    
    methods
        function obj = ziModule(name, device)
            %ZIMODULE Construct an instance of this class
            %   Detailed explanation goes here            
            obj.device = device;
            obj.session = MeasurementSession.MeasurementSession.getSession;
            if nargin == 2                
                obj.zi = obj.session.getDevice(device);
            end
            
            switch name
                case 'sweep'
                    obj.name = name;
                    obj.hdl = ziDAQ('sweep');
                case {'spectrum', 'plotter', 'daq'}
                    obj.name = 'dataAcquisitionModule';
                    obj.hdl = ziDAQ('dataAcquisitionModule');
                case 'scope'
                    obj.name = 'scopeModule';
                    obj.hdl = ziDAQ('scopeModule');
                otherwise
                    obj.name = name;
                    obj.hdl = ziDAQ(name);
            end
            
            obj.clockbase = double(obj.zi.getIntCmd(['/' obj.device '/clockbase']));
            
            if ~strcmp(name, 'scope')
                obj.setParam('device', device);
            end
            
            obj.available_demod_channel = obj.zi.command('listNodes', ['/' device '/demods/']);
            obj.setting_structure = obj.getParam();
            obj.createMonitor();
        end
        
        function addToSession(obj, tag)
            % add obj.measured_data to session
            % input: tag - data name
            if nargin == 1
                tag = input('Please input a name of the measured data: ', 's');
                tag = strrep(tag, ' ', '_');
                tag = strrep(tag, '.', '_');
            end
            obj.session.addMeasurementData(tag, obj.measured_data);
        end
        
        function data = pollModule(~, duration, timeout)
            data = ziDAQ('poll', duration, timeout);
            L=log4m.getLogger;
            L.trace('ziModule::pollModule', sprintf('ziDAQ(''poll'', %f, %f)', duration, timeout) );
        end
        
        function syncModule(~)
            ziDAQ('sync');
            L=log4m.getLogger;
            L.trace('ziModule::syncModule', sprintf('ziDAQ(''sync'')') );
        end
        function tf = finishedModule(obj)
            tf = ziDAQ('finished', obj.hdl);
            L=log4m.getLogger;
            L.trace('ziModule::finishedModule', sprintf('ziDAQ(''finished'', %d) => %d', obj.hdl, tf) );
        end
        function prog = progressModule(obj)
            prog = ziDAQ('progress', obj.hdl);
            L=log4m.getLogger;
            L.trace('ziModule::progressModule', sprintf('ziDAQ(''progress'', %d) => %f', obj.hdl, prog) );
        end
        function executeModule(obj)
            ziDAQ('execute', obj.hdl);
            L=log4m.getLogger;
            L.trace('ziModule::executeModule', sprintf('ziDAQ(''execute'', %d)', obj.hdl) );
        end
        
        function  subscribeNode(obj, node)
            ziDAQ('subscribe', obj.hdl, node);
            L=log4m.getLogger;
            L.trace('ziModule::subscribeNode', sprintf('ziDAQ(''subscribe'', %d, ''%s'')', obj.hdl, node) );
        end
        function  unsubscribeNode(obj, node)
            if nargin == 1
                node = '*';
            end
            ziDAQ('unsubscribe', obj.hdl, node);
            L=log4m.getLogger;
            L.trace('ziModule::unsubscribeNode', sprintf('ziDAQ(''unsubscribe'', %d, ''%s'')', obj.hdl, node) );
        end
        
        function moduleSet(obj, node, value)
            % set Module variable
            ziDAQ('set', obj.hdl, node, value);
            L=log4m.getLogger;
            if ischar(value)
                L.trace('ziModule::moduleSet', sprintf('ziDAQ(''set'', %d, %s, %s)', obj.hdl, node, value) );
            elseif isinteger(value)
                L.trace('ziModule::moduleSet', sprintf('ziDAQ(''set'', %d, %s, %d)', obj.hdl, node, value) );
            elseif isnumeric(value)
                L.trace('ziModule::moduleSet', sprintf('ziDAQ(''set'', %d, %s, %f)', obj.hdl, node, value) );
            end
        end
        function val = moduleGet(obj, node)
            % get Module variable
            val = ziDAQ('get', obj.hdl, node);
            L=log4m.getLogger;
            L.trace('ziModule::moduleSet', sprintf('ziDAQ(''get'', %d, %s, %s)', obj.hdl, node) );
        end
        
        function delete(obj)
            % destructor: unsubscribe all nodes, clear module handle, and
            % close monitor window
            ziDAQ('unsubscribe', obj.hdl, '*');
            ziDAQ('clear', obj.hdl);
            try
                close(obj.monitor_hdl.Parent); 
            catch
                % no monitor window
            end
            L=log4m.getLogger;
            L.info('ziModule::delete', [obj.name ' module deleted.']);
        end
        
        function obj = reset(obj)
            % reset module: clear all subscribed_paths, cla if monitor
            % exists, if not, create a monitor window
            obj.subscribed_paths = [];
            try
                cla(obj.monitor_hdl);  
            catch
                obj.createMonitor;
            end
            L=log4m.getLogger;
            L.info('ziModule::reset', [obj.name ' module reset.']);
        end
        
        %% module settings IO
        function obj = setParam(obj, param_node, val)
            % set module parameter(s)
            if ~ismember(param_node, obj.setting_name_readonly)
                if iscell(val)
                    ziDAQ('set', obj.hdl, [obj.name '/' param_node], char(val{1}) );
                else
                    ziDAQ('set', obj.hdl, [obj.name '/' param_node], val);
                end
            else
                warning('Read-only setting ''%s'' is ignored.', param_node);
            end
        end
        
        function val = getParam(obj, param_node)
            % get module parameter(s)
            if nargin == 1
                param_node = '*';
            end
            
            strct = ziDAQ('get', obj.hdl, [obj.name '/' param_node]);
            try
                val = strct.(param_node);
                if iscell(val)
                    val = char(val{1});
                end
            catch
                val = strct;
            end
        end
        
        function showParam(obj, node)
            % disp module parameters
            fprintf('\n================ Parameters of %s ================\n\n', obj.name);
            if nargin == 1
            	node = '*';
            end
            param = obj.getParam(node);
            disp(param);
            fprintf('==================== End of Parameters ====================\n\n');
        end
        
        %% finish and read
        function obj = finish(obj)
            % finish module: ziDAQ('finish', obj.hdl)
            obj.zi.command('finish', obj.hdl);
        end
        
        function res = read(obj)
            % read module data: ziDAQ('read', obj.hdl)
            res = obj.zi.command('read', obj.hdl);
        end
        
        %% Monitor Handle
        function obj = setMonitorScale(obj, scaleX, scaleY)
            % set monitor scale: example - setMonitorScale('linear', 'log) 
            obj.monitor_hdl.YScale = scaleX;
            obj.monitor_hdl.XScale = scaleY;
        end
        function hdl = createMonitor(obj)
            % create a monitor window
            figure('name',['[Monitor] /' obj.device '/' obj.name]);  % create a new figure before getting an axes.
            hdl = axes;
            obj.monitor_hdl = hdl;
        end
        function hdl = getMonitorHandle(obj)
            % return the axis handl of the monitor window
            hdl = obj.monitor_hdl;
        end
    end
    %% Deprecated
    methods
        % ============================
        % set/get Param
        % ============================
        function obj = setParamStruct(obj, val_struct)
            % Deprecated
            flat_struct = obj.setting_struct_flatten(val_struct);
            names = fieldnames(flat_struct);
            for k = 1:length(names)
                name_k = strrep(names{k}, '_', '/');
                val_k = flat_struct.(names{k});
                obj.setParam(name_k, val_k);
            end
        end
        
        
        function flat_struct = setting_struct_flatten(obj, s)
            % Deprecated
            if nargin == 1
                s = obj.setting_structure;
            end
            fields = fieldnames(s);
            for k=1:length(fields)
                val_k = s.(fields{k});
                if isstruct(val_k)
                    names = fieldnames(val_k);
                    for j = 1:length(names)
                        flat_struct.([fields{k} '_' names{j}]) = val_k.(names{j});
                    end
                else
                    flat_struct.(fields{k}) = val_k;
                end
            end
            
            fields = fieldnames(flat_struct);
            for k=1:length(fields)
                val_k = flat_struct.(fields{k});
                if iscell( val_k )
                    flat_struct.(fields{k}) = char(val_k{1});
                end 
            end            
        end
        
        function settings = load_settings(obj, filename, variablename)
            % Deprecated
            strct = load(filename, variablename);
            settings =strct.(variablename);
            obj.setParamStruct(settings);
        end
        
        % ============================
        % subscribe/unsubscribe
        % ============================
        function obj = subscribe(obj, node)
            % Deprecated
            ziDAQ('subscribe', obj.hdl, node);
            ziDAQ('setInt', ['/' obj.device '/demods/' obj.record_node_idx_str '/enable'], 1);
        end
        
        function obj = unsubscribe(obj, node)
            % Deprecated
            ziDAQ('unsubscribe', obj.hdl, node);
        end

        
        function result = batchRun(obj, function_hdl, index, varargin)
            % Deprecated
            result = arrayfun(@(k) function_hdl(obj.session, obj, k, varargin{:}), index, 'UniformOutput', false);
        end
        
        function result = batchRunSession(obj, tag, function_hdl, varargin)
            % Deprecated
            try
                indexList = 1:length(obj.session.loop_parameter.(tag));
            catch
                error(['Measurement_session must have "LoopIndex" parameter.' ...
                       'Use, e.g., "session.addParameter(' char(39) 'LoopIndex' char(39) ', 1:10)" before batchSweep_Session.']);
            end
            
            result = arrayfun(@(k) function_hdl(obj.session, obj, k, varargin{:}), indexList, 'UniformOutput', false);
        end

        
        function result = execute(obj, varargin)
            % Deprecated
            p=inputParser;
            p.addParameter('index', 1);
            p.addParameter('silence', 0);
            p.addParameter('processMonitor', @obj.plotData);
            p.parse(varargin{:});
            
            obj.setting_structure = obj.getParam();

            ziDAQ('execute', obj.hdl);
            
            processMonitor = p.Results.processMonitor;
            cla(obj.monitor_hdl);
            textprogressbar( sprintf('%s: Loop index = %d,\t', datestr(now,'HH:MM:SS.FFF, mm/dd/yy'), p.Results.index) );
            
            node=replace(obj.record_node, '.', '_');
            nodeName = lower(replace(obj.node_name, '.', '_'));
            
            tic;
            while ~ziDAQ('finished', obj.hdl)
                
                pause(0.5);
                textprogressbar(ziDAQ('progress', obj.hdl)*100);
                
                tmp = obj.read();
                
                if ziCheckPathInData(tmp, node) && ~p.Results.silence
                    processMonitor(obj.monitor_hdl, tmp.(obj.device).(obj.module_word)(obj.record_node_idx_number).(nodeName){1}, p.Results.index);
                    result = tmp;
                    
                    if strcmp(obj.name, 'scopeModule')
                        obj.finish();
                    end
                else
                    run_time = toc;
                    if strcmp(obj.name, 'scopeModule') && (run_time > obj.trig_time_out)
                        obj.finish();
                        fprintf('\t Scope trigger time out. ');
                        result = [];
                    end
                end
            end
            textprogressbar('.\t done.');
            
            tmp = obj.read();
            
            if ziCheckPathInData(tmp, node) && ~p.Results.silence
                processMonitor(obj.monitor_hdl, tmp.(obj.device).(obj.module_word)(obj.record_node_idx_number).(nodeName){1}, p.Results.index);
                result = tmp;
            end
        end
        
        function res = run(obj, varargin)
            % Deprecated
            clear textprogressbar;
            
            p=inputParser;
            p.addParameter('index', 1);
            p.addParameter('silence', 0);
            p.addParameter('processMonitor', @obj.plotData);
            p.parse(varargin{:});
            
            res = obj.execute('index', p.Results.index, ...
                              'silence', p.Results.silence, ...
                              'processMonitor', p.Results.processMonitor);
            obj.finish();
        end

    end
    %%
    methods (Abstract)
        plotData(obj, ax, sample, index)
    end
end